#import "GlyphView.h"
#import "MenuManager.h"
#import "ClassManager.h"
#import "Terminator.h"
#import "Tray.h"
#import "MenuManager.h"
#import "InstanceManager.h"
#import "GlyphManager.h"
#import "NuPerformer.h"
#import "MKManager.h"
#import "FGWindow.h"
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/NXBrowser.h>
#import <appkit/TextField.h>
#import <appkit/ButtonCell.h>
#import <appkit/SavePanel.h>
#import <defaults/defaults.h>
#import <appkit/publicWraps.h>
#import <musickit/Conductor.h>
#import <musickit/Orchestra.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <c.h>
#import <strings.h>
#import <math.h>

#define GLYPHVIEWVERSION 1

extern id Nu ;
static id glyphViewList ;
const char * glyphTypes[] = {"m",NULL} ;

void NuClearRect(NXRect * aRect) 
{ // use this to clear rectangles
  PSsetgray(1.0) ;
  PSsetalpha(0.0) ;
  PSrectfill(aRect->origin.x,aRect->origin.y,
    aRect->size.width,aRect->size.height) ;
  PSsetgray(0.0) ;
  PSsetalpha(1.0) ;
}

@implementation GlyphView: View
{ @public
  id glyphMsgView, mouseObject ;
  id playButton, freezeButton, lockButton, resizeButton ;
  Glyph *rootGlyph ;
  struct glyphViewFlags flags ;
  char *fileName ;
  // music kit data
  double beatsPerMinute, headRoom, deltaT, samplingRate, tickPeriod ;
  BOOL fastResponse ;
}

+ become: (List *) newIds if: (List *) oldIds views: viewList;
{ // if any existing glyphs are members of oldClass, then
  // they are "coerced" into newClass by changing their
  // "isa" pointers. viewList should be a List object:
  // I add the ids of all glyphViews containing Glyphs whose
  // class changed to this list..
  int viewNum, viewKnt ;
  id theView, theGlyph ;
  viewKnt = [glyphViewList count] ;
  for(viewNum = 0 ; viewNum < viewKnt ; viewNum++)
  { theView = [glyphViewList objectAt: viewNum] ;
    theGlyph = [theView rootGlyph] ;
    if([self glyph: theGlyph become: newIds if: oldIds])
      [viewList addObject: theView] ;
  }
  return self ;
}

+ cursor ;
{ // draws and sets the Nutation cursor
  static id cursor = nil ;
  if(!cursor)
  { id anImage ;
    NXSize aSize = {16.0,16.0} ;
    NXPoint hotSpot = {7.0,7.0} ;
    anImage = [[NXImage alloc] initSize: &aSize] ;
    [anImage lockFocus] ;
    PSsendstring(
      "0 setalpha "
     "1 setgray "
     "0 0 16 16 rectfill "
     "1 setalpha "
     "0 setgray "
     "1 setlinewidth "
     "3 3 moveto "
     "6 6 lineto "
     "3 2 moveto "
     "5 4 lineto "
     "2 3 moveto "
     "4 5 lineto "
      "9 9 moveto "
     "12 12 lineto "
     "11 10 moveto "
     "13 12 lineto "
     "10 11 moveto "
     "12 13 lineto "
     "3 12 moveto "
     "5 10 lineto "
      "3 13 moveto "
      "4 12 lineto "
      "2 12 moveto "
      "3 11 lineto "
     "9 6 moveto "
     "11 4 lineto "
     "11 5 moveto "
     "12 4 lineto "
     "10 4 moveto "
     "11 3 lineto "
     "stroke ") ;              
    PScvx() ;
    PSexec() ;
    [anImage unlockFocus] ;
    cursor = [[NXCursor alloc] initFromImage: anImage] ;
    [cursor setHotSpot: &hotSpot] ;
  }
  return cursor ;
}

// need this to get ahold of glyph's "private"
// isa instance var
struct glyphDef
{ @defs(Glyph)
} ;

+ (BOOL) glyph: (Glyph *) aGlyph become: (List *) newList if: (List *) oldList ;
{ // if aGlyph->isa is in oldList, change it to 
  // corresponding value on newList.
  // This should only be called on the root of a TTree.
  // It recurses through all Glyphs in the tree, and
  // returns YES iff at least one Glyph in the
  // tree "became" a value on newList.
  // Also descends the super_class chain to see if a super_class
  // needs to be replaced.
  BOOL rval = NO ;
  struct glyphDef *theGlyph ;
  struct objc_class *theClass ;
  int i, knt ;
  knt = [newList count] ;
  if(aGlyph->flags.isTerminator)
    return NO ;
  theGlyph = (struct glyphDef *) aGlyph ;
  for(i = 0 ; i < knt ; i++)
  { if(theGlyph->isa == (Class) [oldList objectAt: i])
    { theGlyph->isa = (Class) [newList objectAt: i] ;
      rval = YES ;
      break ;
    }
  }
  if(!rval) // search up superclass chain
  { theClass = theGlyph->isa ;
    while(theClass->super_class)
    { for(i = 0 ; i < knt ; i++)
      { if(theClass->super_class == (Class) [oldList objectAt: i])
        { theClass->super_class = (Class) [newList objectAt: i] ;
          rval = YES ;
	  break ;
        }
      }
      if(rval)
        break ;
      theClass = theClass->super_class ;
    }
  }
  rval = [self glyph: aGlyph->is become: newList if: oldList] || rval ;
  rval = [self glyph: aGlyph->then become: newList if: oldList] || rval ;
  return rval ;
}

+ glyphViewList ;
{ // return the list of glyph views
  return glyphViewList ;
}

+ initialize ;
{ [self setVersion: GLYPHVIEWVERSION] ;
  glyphViewList = [[List alloc] init] ;
  return self ;
}

- (BOOL) acceptsFirstResponder ;
{ return YES ;
}

- browse: sender ;
{ // open a ClassManager on the targetGlyph
  // Keep this in glyphView
  char *aClass = [[[Nu targetGlyph] class] name] ;
  if(! [[Nu glyphManager] editFile: nil class: aClass])
  NXRunAlertPanel("Nu","Error: can't find class definition "
    "for class %s\n", NULL,NULL,NULL,aClass) ;
  return self ;
}

- clearGlyphMsgView: aView ;
{ if(glyphMsgView)
    [glyphMsgView setStringValue: ""] ;
  return self ;
}

-displayRaised: sender ;
{ return [rootGlyph displayRaised] ;
}

- drawSelf:(const NXRect *)rects :(int)rectCount ;
{ if(NXDrawingStatus == NX_PRINTING)
    return [rootGlyph printSelf: rects :rectCount] ;
  else
    return [rootGlyph drawSelf: rects :rectCount] ;
}

- fileName: (char *) fname ;
{ // copy fname into ivar, set the
  // window's title to fname, with the
  // filename followed by the path
  char fullName[128], *name ;
  if(fileName)
    free(fileName) ;
  fileName = (char *) malloc(strlen(fname) + 3) ;
  strcpy(fileName, fname) ;
  strcpy(fullName, fileName) ;
  name = rindex(fullName,'/') ;
  if(name != NULL) // is there is a path,
  { char title[129] ;
    name[0] = '\0' ; // split string into 2 pieces
    name++ ;
    sprintf(title,"%s  %s",name,fullName) ;
    [window setTitle: title] ;
  }
  else // no path, just use the filename
    [window setTitle: fname] ;
  return self ;
}

- free ;
{ free(fileName) ;
  return [super free] ;
}

- freezeButton ;
{ return freezeButton ;
}

- freezeButton: sender ;
{ // freeze button pressed...
  // Just relay this to the rootGlyph
  [rootGlyph freezeButton: freezeButton] ;
  return self ;
}


- glyphMsg: (char *) aMsg ;
{ // flash the msg in my message view
  if(glyphMsgView)
  { [glyphMsgView setStringValue: aMsg] ;
   [self perform: @selector(clearGlyphMsgView:) with: self
        afterDelay: 1500 cancelPrevious: YES] ;
  }
  return self ;
}

- inspect: sender ;
{ // open an instance manager on target glyph
  // all the work is handled by the menuManager
  return [Nu instance: self] ;
}

- lock: sender ;
{ // relay this action message
  [rootGlyph lock] ;
  return self ;
}

- lockButton ;
{ // return id of the lock button
  return lockButton ;
}

- mouseDown: (NXEvent *) anEvent ;
{ // normally, mouseObject is set to
  // the rootGlyph, but it is set to
  // the inspectorManager during resizing.
  return [mouseObject mouseDown: anEvent] ;
}

- mouseDragged: (NXEvent *) anEvent ;
{ [window setDocEdited: YES] ;
  return [mouseObject mouseDragged: anEvent] ;
}

- mouseUp: (NXEvent *) anEvent ;
{ return [mouseObject mouseUp: anEvent] ;
}


- play:sender ;
{ return [rootGlyph play: playButton] ;
}

- resize: sender ;
{ // go into (or out of, i.e. toggle) resizing "mode" by changing
  // the mouseObject ivar to divert mouse events
  // to the instanceManager (or, if leaving the mode,
  // by redirecting mouse events to saved mouseObject) 
  id instanceManager = [Nu instanceManager] ;
  if(!flags.resizing) // enter the mode
  { flags.resizing = YES ;
    if([rootGlyph targetGlyph] == rootGlyph)
    { // don't allow resizing or the rootGlyph
      [resizeButton performClick: self] ;
      return self ;
    }
    mouseObject = instanceManager  ; 
    [self setCursor: [[instanceManager class] cursor]] ;
    [instanceManager startSizer: self] ;
  }
  else // exit the mode--replace mouseObject with targetGlyph
  { [instanceManager stopSizer: self] ;
    [self setCursor: [[self class] cursor]] ;
    flags.resizing = NO ;
    mouseObject = rootGlyph ;
  }
  return self ;
}


- resizeButton ;
{ // return id of resizeButton
  return resizeButton ;
}



- revert: sender ;
{ int rval ;
  // if in the glyph borwser, or if not edited, just return
  if(!flags.freeWhenClosed || ![window isDocEdited])
    return self ;
  rval = NXRunAlertPanel(
       "Revert","Window has unsaved changes. Revert?",
        "Revert","Don't Revert", NULL,
        fileName ? fileName: "Untitled") ;
  if (rval == NX_ALERTDEFAULT) // then revert...
  { if(fileName)
    { // put fileName into NULL terminated list,
      // get Menu Manager to open it from disk
      char *fileNames[2] ;
      fileNames[0] = fileName ;
      fileNames[1] = NULL ;
      [Nu openList: fileNames path: NULL] ;
    }
    else // file was never named, hence was brand new...
      [Nu newGlyphWindow: self] ;
    // now schedule my own demise
    [window setDocEdited: NO] ;
    [window perform: @selector(performClose:)
        with: self afterDelay:1 cancelPrevious: YES] ;
  }
  // else don't revert
  return self ;
}


- rightMouseDown: (NXEvent *) anEvent ;
{ return [mouseObject rightMouseDown: anEvent] ;
}

- rightMouseDragged: (NXEvent *) anEvent ;
{ return [mouseObject rightMouseDragged: anEvent] ;
}


- rightMouseUp: (NXEvent *) anEvent ;
{ return [mouseObject rightMouseUp: anEvent] ;
}

- rootGlyph ;
{ return rootGlyph ;
}

- rootGlyph: aGlyph ;
{ rootGlyph = aGlyph ;
  return self ;
}

-(BOOL) save:sender ;
{ if(fileName == NULL) 
    return [self saveAs: sender] ;
  else
    return [self saveToFile] ;
  return YES ;
}

-(BOOL) saveAs:sender ;
{ id savePanel = [SavePanel new] ;
  [savePanel setRequiredFileType: "Nu"] ;
  if([savePanel runModalForDirectory: NULL file: fileName])
  { [self fileName: (char *) [savePanel filename]] ;
    return [self saveToFile] ;
  }
  else
    return NO ;
}


-(BOOL) saveToFile ;
{ // save my window and all its subviews and
  // subglyphs to fileName
  NXStream *aStream ;
  if(aStream = NXOpenTypedStreamForFile(fileName, NX_WRITEONLY))
  { // make rootGlyph the target before saving
    id targetGlyph = [rootGlyph targetGlyph] ;
    [rootGlyph becomeTarget] ;
    NXWriteRootObject(aStream,window) ;
    NXCloseTypedStream(aStream) ;
    [targetGlyph becomeTarget] ;
    [window setDocEdited: NO] ;
    return YES ;
  }
  return NO ;
}


- setCursor: aCursor ;
{ // make aCursor the cursor to use when mouse
  // is in this view
  [[superview superview] setDocCursor: aCursor] ;
  [window resetCursorRects] ;
  return self ;
}


/**
 ** music kit manager "set" methods... these methods are
 ** called by the manager on user interaction...they keep
 ** the glyphviews ivars in sync with the manager
 **/

- setMKMBeatsPerMinute: (double) bPM ;
{ beatsPerMinute =  bPM ;
  return self ;
}

- setMKMDeltaT: (double) seconds ;
{ deltaT =  seconds ;
  return self ;
}

- setMKMFastResponse: (BOOL) YESorNO ;
{ fastResponse = YESorNO ;
  return self ;
}

- setMKMHeadRoom: (double) seconds ;
{ headRoom = seconds ;
  return self ;
}

- setMKMTickPeriod: (double) tP ;
{ tickPeriod =  tP ;
  return self ;
}

- setMKMSamplingRate: (double) sR ;
{ samplingRate = sR ;
  return self ;
}

/*********/

- setFreeWhenClosed: (BOOL) YESorNO ;
{ // set whether I free myself and glyphs when
  // my window closes.  default is YES ;
  flags.freeWhenClosed = YESorNO ;
  return self ;
}

- setGlyphMsgView: anObject ;
{ // by making this method explicit, the GlyphManager
  // can reset the glyphMsgView to its own msgView.
  glyphMsgView = anObject ;
  [Nu glyphView: self] ;
  [self setupNew] ;
  return self ;
}


- setupArchived
{ // perform setup after unarchived
  [rootGlyph setup] ;
  return [self setupCommon] ;
}

- setupCommon ;
{ // perform setup common to both new and
  // unarchived glyphViews
  id mKM  ;
  [[GlyphView glyphViewList] addObject: self] ;
  [window useOptimizedDrawing: YES] ;
  [window setDocEdited: NO] ;
  [window display] ;
  [self setCursor: [[self class] cursor]] ;
  [rootGlyph display: &bounds from: nil] ;
  [window setMiniwindowIcon: "gwIcon.tiff"] ;
  // get the music kit values, using what
  // is currently displayed in the manager's window
  mKM = [Nu musicKitManager] ;
  beatsPerMinute = [mKM beatsPerMinute] ;
  headRoom = [mKM headRoom] ;
  deltaT = [mKM deltaT] ;
  samplingRate = [mKM samplingRate] ;
  tickPeriod = [mKM tickPeriod] ;
  fastResponse = [mKM fastResponse] ;
  return self ;
}

- setupNew ;
{ // perform initial setup of a
  // brand new window
  id clipView = superview ;
  flags.freeWhenClosed = YES ; 
  [window addToEventMask: NX_LMOUSEDOWNMASK | NX_RMOUSEDOWNMASK
     | NX_LMOUSEDRAGGEDMASK | NX_RMOUSEDRAGGEDMASK 
     | NX_LMOUSEUPMASK | NX_RMOUSEUPMASK] ;
  // NOTE: this must come BEFORE you alloc init:: the rootGlyph,
  // as the rootGlyph must be able to get my id (via Nu).
  [Nu glyphView: self] ; 
  // setup the rootGlyph and the mouseObject
  rootGlyph = mouseObject = 
    [[objc_getClass("Root") alloc] init:frame.size.width
                 :frame.size.height] ;
  // make sure scrollview autosizes its subviews
  [[clipView superview] setAutoresizeSubviews: YES] ;
  [clipView setAutosizing: NX_HEIGHTSIZABLE | NX_WIDTHSIZABLE] ;
  [clipView setAutoresizeSubviews: YES] ;
  return [self setupCommon] ;
}  


- sizeTo: (float) width :(float) height ;
{ // glyphViews can only grow, they are not
  // allowed to shrink
  NXSize newSize ;
  id clipView = superview ;
  [super sizeTo: newSize.width = MAX(bounds.size.width,width)
              : newSize.height = MAX(bounds.size.height,height)] ;
  [rootGlyph sizeTo: newSize.width :newSize.height] ;
  [[clipView superview] reflectScroll: clipView] ;
  return self ;
}

- targetGlyph ;
{ return [rootGlyph targetGlyph] ;
}

- (int) tag ;
{ return GLYPHVIEWTAG ;
}


- updateMKManager ;
{ // copy my music kit data into the MKManager window
  id m ;
  m = [Nu musicKitManager] ;
  [m beatsPerMinute: beatsPerMinute] ;
  [m headRoom: headRoom] ;
  [m deltaT: deltaT] ;
  [m samplingRate: samplingRate] ;
  [m tickPeriod: tickPeriod] ;
  [m fastResponse: fastResponse] ;
  return self ;  
}

- windowDidBecomeMain: sender ;
{  // inform Nu that I have become the main glyphView
  [Nu glyphView: self] ;
  // update the music kit defaults
  [self updateMKManager] ;
  return self ;
}

- windowDidResignMain: sender ;
{ return self ;
} 


- windowWillClose: sender ;
{ // our window is about to close; need to free up 
  // stuff we created if window will be freed 

  if(flags.freeWhenClosed)
  { // in the glyph browser's glyphView, flags.freeWhenClosed = NO,
    // i.e. we never free it 
    if([window isDocEdited])
    { int rval = NXRunAlertPanel(
       "Close","Save changes to %s?",
        "Save","Don't Save","Don't Close",
        fileName ? fileName: "Untitled") ;
      if (rval == NX_ALERTOTHER)
        return NO ;
      else if (rval == NX_ALERTDEFAULT)
        [self save: self] ;
      // else just close
    }
    if([Nu inspecting])
      if([[Nu instanceManager] targetGlyph] == [rootGlyph targetGlyph])
        [[Nu instanceManager] orderOut: self] ;
    // clear any delayed message
    // Historical note: in 2.0, afterDelay: 0 did the job.
    // In 3.0, this didn't work, but -1 did.  Go figure.
    [self perform: @selector(clearGlyphMsgView:) with: self
       afterDelay: -1 cancelPrevious: YES] ;
    [rootGlyph basicFree] ;
    [[GlyphView glyphViewList] removeObject: self] ;
    // set current glyphView to that of the glyphManager
    [Nu glyphView: [[Nu glyphManager] glyphView]] ;
  }
  return self ;
} 

-read: (NXTypedStream *) stream ;
{ int version ;
  version = NXTypedStreamClassVersion(stream,[[self class] name]) ;
  [super read: stream] ;
  [Nu glyphView: self] ;
  glyphMsgView = NXReadObject(stream) ;
  freezeButton = NXReadObject(stream) ;
  resizeButton = NXReadObject(stream) ;
  lockButton = NXReadObject(stream) ;
  playButton = NXReadObject(stream) ;
  rootGlyph = mouseObject = NXReadObject(stream) ;
  NXReadType(stream,"s",(void *) &flags) ;
  // unarchive music kit parameters
  NXReadTypes(stream,"fffffc",&beatsPerMinute,&headRoom,&deltaT,
      &samplingRate,&tickPeriod,&fastResponse) ; 
  [self updateMKManager] ;
  return self ;
}

-write: (NXTypedStream *) stream ;
{ [super write: stream] ;
  NXWriteObjectReference(stream, glyphMsgView) ;
  NXWriteObjectReference(stream, freezeButton) ;
  NXWriteObjectReference(stream, resizeButton) ;
  NXWriteObjectReference(stream, lockButton) ;
  NXWriteObjectReference(stream, playButton) ;
  NXWriteObject(stream, rootGlyph) ;
  NXWriteType(stream,"s",(void *) &flags) ;
  NXWriteTypes(stream,"fffffc",&beatsPerMinute,&headRoom,&deltaT,
       &samplingRate,&tickPeriod,&fastResponse) ;
  return self ;
}

@end


